#include "SlidingStackedWidget.h"
#include <QParallelAnimationGroup>
#include <QPropertyAnimation>

SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)

	: QStackedWidget(parent)
{
	if (parent != nullptr)
	{
		m_mainwindow = parent;
	}
	else
	{
		m_mainwindow = this;
	}

	// parent should not be 0; at least not tested for any other case yet !!
	// Now, initialize some private variables with default values
	m_vertical      = false;
	// setVerticalMode(true);
	m_speed         = 500;
	m_animationtype = QEasingCurve::OutBack;     // check out the QEasingCurve documentation for different styles
	m_now           = 0;
	m_next          = 0;
	m_wrap          = false;
	m_pnow          = QPoint(0, 0);
	m_active        = false;
}

void SlidingStackedWidget::setVerticalMode(bool vertical)
{
	m_vertical = vertical;
}

void SlidingStackedWidget::setSpeed(int speed)
{
	m_speed = speed;
}

void SlidingStackedWidget::setAnimation(enum QEasingCurve::Type animationtype)
{
	m_animationtype = animationtype;
}

void SlidingStackedWidget::setWrap(bool wrap)
{
	m_wrap = wrap;
}

void SlidingStackedWidget::slideInNext()
{
	int now = currentIndex();

	if (m_wrap || (now < count() - 1))
	{
		// count is inherit from QStackedWidget
		slideInIdx(now + 1);
	}
}

void SlidingStackedWidget::slideInPrev()
{
	int now = currentIndex();

	if (m_wrap || (now > 0))
	{
		slideInIdx(now - 1);
	}
}

void SlidingStackedWidget::slideInIdx(int              idx,
                                      enum t_direction direction)
{
	// int idx, t_direction direction=AUTOMATIC
	if (idx > count() - 1)
	{
		direction = m_vertical ? TOP2BOTTOM : RIGHT2LEFT;
		idx       = (idx) % count();
	}
	else if (idx < 0)
	{
		direction = m_vertical ? BOTTOM2TOP : LEFT2RIGHT;
		idx       = (idx + count()) % count();
	}

	slideInWgt(widget(idx), direction);
	// widget() is a function inherited from QStackedWidget
}

void SlidingStackedWidget::slideInWgt(QWidget         *newwidget,
                                      enum t_direction direction)
{
	if (m_active)
	{
		return;        // at the moment, do not allow re-entrance before an animation is completed.
		// other possibility may be to finish the previous animation abrupt, or
		// to revert the previous animation with a counter animation, before going ahead
		// or to revert the previous animation abrupt
		// and all those only, if the newwidget is not the same as that of the previous running animation.
	}
	else
	{
		m_active = true;
	}

	enum t_direction directionhint;
	int              now  = currentIndex(); // currentIndex() is a function inherited from QStackedWidget
	int              next = indexOf(newwidget);

	if (now == next)
	{
		m_active = false;
		return;
	}
	else if (now < next)
	{
		directionhint = m_vertical ? TOP2BOTTOM : RIGHT2LEFT;
	}
	else
	{
		directionhint = m_vertical ? BOTTOM2TOP : LEFT2RIGHT;
	}

	if (direction == AUTOMATIC)
	{
		direction = directionhint;
	}

	// NOW....
	// calculate the shifts
	int offsetx = frameRect().width();  // inherited from mother
	int offsety = frameRect().height(); // inherited from mother
	// the following is important, to ensure that the new widget
	// has correct geometry information when sliding in first time
	widget(next)->setGeometry(0,  0, offsetx, offsety);

	if (direction == BOTTOM2TOP)
	{
		offsetx = 0;
		offsety = -offsety;
	}
	else if (direction == TOP2BOTTOM)
	{
		offsetx = 0;
		// offsety=offsety;
	}
	else if (direction == RIGHT2LEFT)
	{
		offsetx = -offsetx;
		offsety = 0;
	}
	else if (direction == LEFT2RIGHT)
	{
		// offsetx=offsetx;
		offsety = 0;
	}

	// re-position the next widget outside/aside of the display area
	QPoint pnext = widget(next)->pos();
	QPoint pnow  = widget(now)->pos();
	m_pnow = pnow;
	widget(next)->move(pnext.x() - offsetx, pnext.y() - offsety);
	// make it visible/show
	widget(next)->show();
	widget(next)->raise();
	// animate both, the now and next widget to the side, using animation framework
	QPropertyAnimation      *animnow = new QPropertyAnimation(widget(now), "pos");
	animnow->setDuration(m_speed);
	animnow->setEasingCurve(m_animationtype);
	animnow->setStartValue(QPoint(pnow.x(), pnow.y()));
	animnow->setEndValue(QPoint(offsetx + pnow.x(), offsety + pnow.y()));
	QPropertyAnimation      *animnext = new QPropertyAnimation(widget(next), "pos");
	animnext->setDuration(m_speed);
	animnext->setEasingCurve(m_animationtype);
	animnext->setStartValue(QPoint(-offsetx + pnext.x(), offsety + pnext.y()));
	animnext->setEndValue(QPoint(pnext.x(), pnext.y()));
	QParallelAnimationGroup *animgroup = new QParallelAnimationGroup;
	animgroup->addAnimation(animnow);
	animgroup->addAnimation(animnext);
	QObject::connect(animgroup, SIGNAL(finished()), this, SLOT(animationDoneSlot()));
	m_next   = next;
	m_now    = now;
	m_active = true;
	animgroup->start();
	// note; the rest is done via a connect from the animation ready;
	// animation->finished() provides a signal when animation is done;
	// so we connect this to some post processing slot,
	// that we implement here below in animationDoneSlot.
}

void SlidingStackedWidget::animationDoneSlot()
{
	// when ready, call the QStackedWidget slot setCurrentIndex(int)
	setCurrentIndex(m_next); // this function is inherit from QStackedWidget
	// then hide the outshifted widget now, and  (may be done already implicitely by QStackedWidget)
	widget(m_now)->hide();
	// then set the position of the outshifted widget now back to its original
	widget(m_now)->move(m_pnow);
	// so that the application could also still call the QStackedWidget original functions/slots for changings
	// widget(m_now)->update();
	// setCurrentIndex(m_next);  //this function is inherit from QStackedWidget
	m_active = false;
	emit animationFinished();
}